﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ComLib.Logging;

namespace TwlNS {
	public abstract class Environment {
		public static int RadioRadius { get; set; }
		public static int SensingRadius { get; set; }
		public static int RandomVariableSeed { get; set; }
		public static bool UseRandomVariableSeed { get; set; }
		public static double HopCountWeight { get; set; }
		public static double EnergyWeight { get; set; }
		public static bool OriginalImplementation { get; set; }
		public static bool N2 { get; set; }

		public static int MaxNumOfSets { get; set; }

		public static int NumInitPackets = 0;
		public static int NumSuccessPackets = 0;

		protected List<Node> _Nodes;
		public List<Node> Nodes {
			get { return _Nodes; }
		}

		protected List<SinkNode> _SinkNodes;
		public List<SinkNode> SinkNodes {
			get { return _SinkNodes; }
		}



		protected double _Width;
		public double Width {
			get { return _Width; }
		}
		protected double _Height;
		public double Height {
			get { return _Height; }
		}

		public int NumActiveNodes {
			get {
				int c = 0;
				foreach (Node n in _Nodes) {
					if (n is SensorNode && n.Activated)
						c++;
				}
				return c;
			}
		}

		public int NumOfNodes(int setNumber) {
			int count = 0;
			foreach (var node in Nodes) {
				if (node.SetNumbers.Contains(setNumber))
					count++;
			}
			return count;
		}

		public void InitalizeNodes() {
			Logger.Info("Initializing Nodes");
			List<Node> nodesToRemove = new List<Node>();
			foreach (var node in Nodes) {
				if (node.Energy < 0) {
					Logger.Info("Remove depleted node " + node.Id);
					nodesToRemove.Add(node);
				}
			}
			foreach (var node in nodesToRemove) {
				Nodes.Remove(node);
			}

			DateTime t = DateTime.Now;
			Logger.Info("Creating Links ...");
			Logger.Flush();
			CreateLink();
			ShowTimeConsumed(ref t);

			Logger.Info("Flooding Hop Counts ...");
			Logger.Flush();
			FloodHopCount();
			ShowTimeConsumed(ref t);

			Logger.Info("Finding Parents ...");
			Logger.Flush();
			FindParent();
			ShowTimeConsumed(ref t);

			Logger.Info("Determine Sets ...");
			Logger.Flush();
			DetermineSets();
			ShowTimeConsumed(ref t);
		}

		void ShowTimeConsumed(ref DateTime startTime) {
			DateTime t2 = DateTime.Now;
			double totalms = (t2 - startTime).TotalMilliseconds;
			Logger.Info("    Times Consumed: " + totalms / 1000.0 + " seconds.");
			Logger.Flush();
			startTime = t2;
		}

		public virtual void AddNodesAndRandomDistribute(List<Node> nodes) {
			_Nodes = nodes;
			_SinkNodes = new List<SinkNode>();
			foreach (var node in Nodes) {
				if (node is SinkNode)
					_SinkNodes.Add((SinkNode)node);
			}
		}

		public void CreateLink() {
			//*
			int xMax = (int)Math.Ceiling(_Width / RadioRadius);
			int yMax = (int)Math.Ceiling(_Height / RadioRadius);
			var matrix = new List<Node>[xMax][];
			for (int x = 0; x < xMax; x++) {
				matrix[x] = new List<Node>[yMax];
				for (int y = 0; y < yMax; y++) {
					matrix[x][y] = new List<Node>();
				}
			}
			foreach (var node in Nodes) {
				int x = (int)Math.Floor(node.Location.X / RadioRadius);
				int y = (int)Math.Floor(node.Location.Y / RadioRadius);
				matrix[x][y].Add(node);
			}
			for (int x = 0; x < xMax; x++) {
				for (int y = 0; y < yMax; y++) {
					var list = matrix[x][y];
					for (int i = 0; i < list.Count; i++) {
						var node = list[i];
						AddRadioRangeNeighbor(node, list, i + 1);
						if (x - 1 >= 0)
							AddRadioRangeNeighbor(node, matrix[x - 1][y], 0);
						if (y - 1 >= 0)
							AddRadioRangeNeighbor(node, matrix[x][y - 1], 0);
						if (x - 1 >= 0 && y - 1 >= 0)
							AddRadioRangeNeighbor(node, matrix[x - 1][y - 1], 0);
						if (x - 1 >= 0 && y + 1 < yMax)
							AddRadioRangeNeighbor(node, matrix[x - 1][y + 1], 0);
					}
				}
			}
			//foreach (var node in Nodes) {
			//	node.SortRadioRangeNeighbor();
			//}
			/*/
			for (int i = 0; i < Nodes.Count; i++) {
				Coord n0 = Nodes[i].Location;
				for (int j = i + 1; j < Nodes.Count; j++) {
					Coord n1 = Nodes[j].Location;
					if (Math.Abs(n0.X-n1.X)<=RadioRadius && Math.Abs(n0.Y-n1.Y)<=RadioRadius &&
						Nodes[i].Location.distance(Nodes[j].Location) <= RadioRadius) {
						Nodes[i].AddRadioRangeNeighbor(Nodes[j]);
					}
				}
			}
			//*/
		}
		private void AddRadioRangeNeighbor(Node current, IList<Node> list, int startIdx) {
			for (int j = startIdx; j < list.Count; j++) {
				current.AddRadioRangeNeighbor(list[j]);
			}
		}

		public void FloodHopCount() {
			var currentNodes = new HashSet<Node>();
			foreach (var sinkNode in _SinkNodes) {
				currentNodes.Add(sinkNode);
			}
			int maxHopCount = 0;
			while (currentNodes.Count != 0) {
				var nextNodes = new HashSet<Node>();
				//Console.WriteLine("Current HopCount=" + currentNodes[0].HopCount + " # of nodes=" + currentNodes.Count);

				foreach (var currentNode in currentNodes) {
					foreach (var neighbor in currentNode.RadioRangeNeighbor) {
						if (neighbor.AddPossibleParent(currentNode) && !nextNodes.Contains(neighbor)) {
							nextNodes.Add(neighbor);
						}
					}
				}
				currentNodes = nextNodes;
				maxHopCount++;
			}
			foreach (var node in Nodes) {
				node.CleanPossibleParent();
			}
			Logger.Info("Max Hop Count=" + (maxHopCount - 1));
		}
		public void FindParent() {
			foreach (var node in Nodes) {
				if (node is SinkNode)
					continue;
				node.FindAndSetParent();
				int k = 4;

			}
			//Old implementation
			/*
			var edgeNodes = new HashSet<Node>();
			foreach (var node in Nodes) {
				if(node.PossibleParents.Count!=0 && node.PossibleChildren.Count==0)
					edgeNodes.Add(node);
			}
			while (edgeNodes.Count != 0) {
				var nextEdges = new HashSet<Node>();
				foreach (var node in edgeNodes) {
					foreach (var parent in node.PossibleParents) {
						if (node.ParentNode == null) {
							if (parent.AddChildren(node)) {
								nextEdges.Add(parent);
							} 
						}
						if(parent.ParentNode==null && !nextEdges.Contains(parent)){
							nextEdges.Add(parent);
						}
					}
				}
				edgeNodes = nextEdges;
			}*/
			foreach (var sinkNode in _SinkNodes) {
				sinkNode.SetSinkedRecursive();
			}
			Logger.Info(string.Format("# of Sinked Nodes: {0}/{1}", Nodes.Count(node => node.Sinked), Nodes.Count));
		}

		public void DetermineSets() {
			var edgeNodes = new HashSet<Node>();
			Random r = new Random();
			foreach (var node in Nodes) {
				if (node.Sinked && node.Children.Count == 0) {
					node.AddSetNumber(r.Next(MaxNumOfSets));
					edgeNodes.Add(node);
				} else if (Environment.OriginalImplementation) {
					node.AddSetNumber(r.Next(MaxNumOfSets));
				}
			}
			while (edgeNodes.Count != 0) {
				var nextEdges = new HashSet<Node>();
				foreach (var node in edgeNodes) {
					foreach (var parentNode in node.ParentNodes) {
						bool notAlreadyAdded = parentNode.AddSetNumber(node.SetNumbers);
						if (notAlreadyAdded && !nextEdges.Contains(parentNode))
							nextEdges.Add(parentNode);
					}
					/*
					if (node.ParentNodes != null){
						node.ParentNodes.AddSetNumber(node.SetNumbers);
						if (!nextEdges.Contains(node.ParentNodes))
							nextEdges.Add(node.ParentNodes);
					}*/
				}
				edgeNodes = nextEdges;
			}
		}

		public void ActivateSet(int setNumber) {
			foreach (var sinkNode in SinkNodes) {
				sinkNode.ActivateSetNumberRecursive(setNumber);
			}
		}
		public Dictionary<int, double> CheckUsageVsHopCount(List<Node> nodes) {
			Dictionary<int, List<Node>> allNodes = new Dictionary<int, List<Node>>();
			foreach (var node in nodes) {
				if (node is SinkNode)
					continue;
				if (!allNodes.ContainsKey(node.HopCount)) {
					allNodes.Add(node.HopCount, new List<Node>());
				}
				var list = allNodes[node.HopCount];
				list.Add(node);
			}
			Dictionary<int, double> stdev = new Dictionary<int, double>();
			foreach (var pair in allNodes) {
				int hopCount = pair.Key;
				var list = pair.Value;
				double sum = 0;
				foreach (var node in list) {
					sum += (Node.InitialEnergy - node.Energy);
				}
				double average = sum / list.Count;
				sum = 0;
				foreach (var node in list) {
					sum += Math.Pow(Node.InitialEnergy - node.Energy - average, 2) / list.Count;
				}
				stdev.Add(hopCount, Math.Pow(sum, 0.5));
			}
			string str = "";
			int count = 0;
			for (int i = 1; i <= stdev.Count; i++){
				str += i + ": " + stdev[i] + "("+allNodes[i].Count+");";
				count += allNodes[i].Count;
			}
			str = "Total Nodes=" + count + " " + str;
			Logger.Info(str);

			return stdev;
		}
	}
}
